var kurento = require("kurento-client");
var path = require("path");
var fs = require("fs");
var shelljs = require('shelljs');
var mqtt = require('mqtt');

class TMqClient {
    constructor() {
        this.config = {
            mqClientId: ['mqid', ((new Date()).getTime()).toString(), parseInt(1000 * Math.random()).toString()].join(''),
            mqUrl: "tcp://hhuciot.mqtt.iot.bj.baidubce.com:1883",
            mqUid: "hhuciot/mcu",
            mqPwd: "9vG6KT6V64S2uS3P"
        }
        this.client = null;
        this.events = {
            onConnect: function() {},
            onDisConnect: function() {},
            onMsg: function(msg, topic) {}
        };
        this.actionHandler = {};
    }
    open() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                if (me.config.mqClientId == null || me.config.mqClientId == "") {
                    me.config.mqClientId = ['mqid', ((new Date()).getTime()).toString(), parseInt(1000 * Math.random()).toString()].join('');
                }
                var options = {
                    username: me.config.mqUid,
                    password: me.config.mqPwd,
                    clientId: me.config.mqClientId
                };
                me.client = mqtt.connect(me.config.mqUrl, options);
                me.client.on('connect', function() {
                    console.log("connected to broker. topic:");
                    if (me.events.onConnect || me.events.onConnect != null) {
                        try {
                            me.events.onConnect();
                        } catch (eer) {
                            console.log(eer);
                        }
                    }
                    success();
                    /*
                            "hh/app/"+config.appCode+"/#",
                            "hh/channel/"+config.channelId+"/#",
                            "hh/client/"+config.clientId+"/#"
                    var topics = [
                        "hh/app/" + me.config.appCode + "/#",
                        "hh/channel/" + me.config.channelId + "/#",
                        "hh/client/" + me.config.mqClientId + "/#",
                        "hh/mcu/" + me.config.mcuId + "/#"
                    ];
                    client.subscribe(topics);
                    */
                });
                me.client.on('close', function() {
                    console.log("connection closed");
                    if (me.events.onDisConnect || me.events.onDisConnect != null) {
                        try {
                            me.events.onDisConnect();
                        } catch (eer) {
                            console.log(eer);
                        }
                    }
                });
                me.client.on('message', function(topic, message) {
                    var msg = JSON.parse(message.toString());
                    try {
                        var isActionHandler = false;
                        if (msg.msgId && msg.msgId != null && msg.msgId != "") {
                            var action = me.actionHandler[msg.msgId];
                            if (action && action != null && action instanceof Function) {
                                isActionHandler = true;
                                action(msg, topic);
                            }
                        }

                        if ((!isActionHandler) && me.events.onMsg || me.events.onMsg != null) {
                            try {
                                me.events.onMsg(msg, topic);
                            } catch (eer) {
                                console.log(eer);
                            }
                        }

                    } catch (exr) {
                        console.log(exr)
                    }
                });
            } catch (er) {
                console.log(er);
                success();
            }
        })
    }
    close() {
        var me = this;
        try {
            if (me.client && me.client != null && me.client.isConnected()) {
                me.client.close();
            }
        } catch (er) {
            console.log(er);
        }
    }
    registAction(ac, handler) {
        var me = this;
        try {
            me.actionHandler[ac] = handler;
        } catch (er) {
            console.log(er);
        }
    }
    subscribe(topics) {
        var me = this;
        try {
            if (me.client && me.client != null && me.client.isConnected()) {
                me.client.subscribe(topics);
            }
        } catch (er) {
            console.log(er);
        }
    }
    sendMsg(topic, msg) {
        var me = this;
        try {
            if (me.client && me.client != null) {
                me.client.publish(topic, JSON.stringify(msg));
            }
        } catch (er) {
            console.log(er);
        }
    }
    sendRespMsg(reqId, topic, msg) {
        var me = this;
        try {
            var sendMsg = JSON.parse(JSON.stringify(msg));
            sendMsg.reqId = reqId;
            me.sendMsg(topic, sendMsg)
        } catch (er) {
            console.log(er);
        }
    }
}
class TKmsModelSvr {
    constructor() {
        this.config = {
            clientId: ['c', (new Date()).getTime(), parseInt(100 * Math.random())].join(""),
            version: "kmsModel.v21",
            recorderPath: "/data/rec",
            kmsUrl: "wss://wj.hhdata.cn:10104/kurento",
            clientKmsUrl: "wss://wj.hhdata.cn:10104/kurento",
            peerOptions: {
                configuration: {
                    "iceServers": [{
                            url: "stun:39.171.53.53:3478"
                        },
                        {
                            urls: ["turn:39.171.53.53:3478"],
                            username: "kurento",
                            credential: "kurento"
                        }
                    ]
                },
                mediaConstraints: {
                    audio: true,
                    video: {
                        width: { min: 1280, ideal: 1920, max: 1920 },
                        height: { min: 720, ideal: 1080, max: 1080 }
                    },
                }
            },
            checkObjTimeout: 120000
        };
        this.kmsObjs = {};
        this.kmsClient = null;
        this.mqClient = new TMqClient();
    };
    start() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                console.log('------------- start kms model --------------');
                console.log('open mq client...');
                me.openMq().then(_ => {
                    console.log('connect to mq server.');
                    console.log('connect to kms server.', me.config.kmsUrl);
                    kurento(me.config.kmsUrl, function(erClient, client) {
                        if (erClient && erClient != null) {
                            console.log('connect to kms faild.!!!!!!!!!!!!!!!');
                            success(null);
                        } else {
                            me.kmsClient = client;
                            console.log('connect kms success...');
                            me.getKmsObjs().then(_ => {
                                console.log('load kms objs:', Object.keys(me.kmsObjs).length);
                                success();
                            });
                        }
                    });
                });
            } catch (error) {
                console.log(error);
                success();
            }
        })
    };
    openMq() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var topics = [
                    "kmsModel/client/offline/#"
                ];
                me.mqClient.subscribe(topics);
                me.mqClient.registAction("offline", (msg, topic) => {
                    console.log(msg, topic);
                    try {
                        var clientId = msg.willMsgClientId;
                        me.closeClientObjects(clientId).then(_ => {
                            console.log('close client objects.');
                        })
                    } catch (error1) {
                        console.log(error1);
                    }
                });
                me.mqClient.open().then(_ => {
                    console.log('mq client is connected.');
                    success()
                })
            } catch (error) {
                console.log(error);
                success();
            }
        })
    };
    closeClientObjects(clientId) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var objs = me.getClientObjects(clientId);
                me.freeObjs(objs).then(_ => {
                    console.log('client offline', clientId);
                    success();
                })
            } catch (error1) {
                console.log(error1);
                success();
            }
        })
    };
    getKmsObjs() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                me.kmsClient.getServerManager().then(_svr => {
                    me.kmsClient.svr = _svr;
                    return _svr.getPipelines();
                }).then(ps => {
                    var assignObj = (obj) => {
                        return new Promise((su, fa) => {
                            try {
                                obj.getTag(me.config.version, function(erTag, tag) {
                                    if (tag && tag != null && tag.length > 0) {
                                        try {
                                            var info = JSON.parse(tag.value);
                                            obj.tag = info;
                                            me.kmsObjs[obj.id] = obj;
                                        } catch (er) {
                                            console.log(er);
                                        }
                                    }
                                    su();
                                });
                            } catch (err) {
                                console.log(err);
                                su();
                            }
                        });
                    };
                    var loadPs = async() => {
                        for (var i = 0; i < ps.length; i++) {
                            var pipe = ps[i];
                            await assignObj(pipe);
                            var objs = await pipe.getChildren();
                            for (var k = 0; k < objs.length; k++) {
                                var obj = objs[k];
                                await assignObj(obj);
                            }
                        }
                        success();
                    };
                    loadPs();
                });
            } catch (error) {
                console.log(error);
                success();
            }
        })
    };
    checkModelObjs() {
        var me = this;
        try {
            var checkObjs = () => {
                return new Promise((su, fa) => {
                    var objs = Object.values(me.kmsObjs);
                    var removeObjIds = [];
                    var check = async() => {
                        if (objs.length > 0) {
                            var obj = objs.shift();
                            var objsId = obj.id;
                            me.kmsClient.getMediaobjectById(objsId, function(err, _o) {
                                if (_o && _o != null) {
                                    removeObjIds.push(objsId);
                                }
                                check();
                            });
                        } else {
                            removeObjIds.forEach(objId => {
                                if (me.kmsObjs[objId] && me.kmsObjs[objId] != null) {
                                    delete me.kmsObjs[objId];
                                }
                            });
                            success();
                        }
                    };
                });
            };
            checkObjs().then(_ => {
                setTimeout(() => {
                    me.checkModelObjs();
                }, me.config.checkObjTimeout);
            })
        } catch (error) {
            console.log(error);
        }
    };
    freeObjs(objs) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var index = 0;
                var free = () => {
                    if (index < objs.length) {
                        objs[index].release().then(_ => {
                            index++;
                            free();
                        })
                    } else {
                        success();
                    }
                };
                free();
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    clearObjs() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var objs = Object.values(me.kmsObjs);
                me.freeObjs(objs).then(_ => {
                    success();
                })
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    registObject(obj, code, clientId, info) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var data = info || {};
                var idArr = obj.id.split(".");
                data.kmsId = obj.id;
                data.kmsType = idArr[idArr.length - 1];
                data.code = code;
                data.clientId = clientId;
                obj.addTag(me.config.version, JSON.stringify(data), function(erJson) {
                    obj.tag = data;
                    kmsObjs[obj.id] = obj;
                    success(obj);
                });
            } catch (error) {
                console.log(error);
            }
        });
    };
    getClientObjects(clientId) {
        var me = this;
        var res = [];
        try {
            res = me.kmsObjs.filter(o => {
                return o.tag && o.tag.clientId && o.tag.clientId == clientId;
            });
        } catch (error) {
            console.log(error);
        }
        return res;
    };
    startRecord(srcId, recFn) {
        var me = this;
        var res = {
            srcFlag: false,
            recorderId: "",
            recordFlag: false,
            recFn: recFn
        }
        return new Promise((success, faild) => {
            try {
                var fullFn = path.join(a.config.recorderPath, recFn);
                if (fullFn.indexOf(".webm") < 0) {
                    fullFn += ".webm";
                }
                var fnDir = path.parse(fullFn).dir;
                if (!fs.existsSync(fnDir)) {
                    shelljs.mkdir('-p', fnDir);
                    if (fs.existsSync(fnDir)) {
                        shelljs.chmod(777, fnDir);
                    }
                }
                me.getClient().then(client => {
                    return client.getMediaobjectById(srcId).then(src => {
                        if (src && src != null) {
                            srcFlag = true;
                            var pipeId = (srcId.split("/"))[0];
                            client.getMediaobjectById(pipeId).then(pipe => {
                                var opts = {
                                    stopOnEndOfStream: true,
                                    uri: 'file://' + fullFn
                                };
                                var recorder = null;
                                pipe.create("RecorderEndpoint", opts).then(_recorder => {
                                    recorder = _recorder;
                                    res.recorderId = recorder.id;
                                    recorder.on("Stopped", function(evData) {
                                        recorder.release();
                                    });
                                    var info = {
                                        modelType: "recorder",
                                        srcId: srcId,
                                        recFn: recFn,
                                        startTm: (new Date()).getTime()
                                    };
                                    return me.registObject(recorder, recFn, "server", info);
                                }).then(_ => {
                                    return src.connect(recorder);
                                }).then(_ => {
                                    return recorder.record();
                                }).then(_ => {
                                    res.recordFlag = true;
                                    success(res);
                                });
                            });
                        } else {
                            success(res);
                        }
                    });
                });
            } catch (er) {
                console.log(er);
                success(res);
            }
        })
    };
    stopRecord(recorderId) {
        var me = this;
        var res = {
            srcFlag: false,
            recorderId: "",
            recordFlag: false,
            recFn: recFn
        }
        return new Promise((success, faild) => {
            try {
                me.getClient().then(client => {
                    return client.getMediaobjectById(recorderId).then(recorder => {
                        if (recorder && recorder != null) {
                            recorder.stopAndWait().then(_ => {
                                success();
                            });
                        } else {
                            success();
                        }
                    });
                });
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };

    //--------------------  model ------------------------

    getSingleChannel(channelCode) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var ch = Object.values(me.kmsObjs).find(obj => {
                    var flag = obj.tag && obj.tag.kmsType == "MediaPipeline" && obj.tag.code == channelCode;
                    return flag;
                });
                if (ch && ch != null) {
                    success(ch);
                } else {
                    me.kmsClient.create("MediaPipeline").then(_pipe => {
                        var info = {
                            model: 'channel',
                            channelCode: channelCode,
                            createTime: (new Date()).getTime()
                        };
                        return me.registObject(_pipe, channelCode, '', info);
                    }).then(_ch => {
                        success(_ch);
                    });
                }

            } catch (error) {
                console.log(error);
                success(null);
            }
        });
    };
    getSingleSeat(channelCode, seatCode) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var seat = Object.values(me.kmsObjs).find(obj => {
                    var flag = obj.tag && obj.tag.kmsType == "PassThrough" && obj.tag.channelCode == channelCode && obj.tag.code == seatCode;
                    return flag;
                });
                if (seat && seat != null) {
                    success(seat);
                } else {
                    me.getSingChannel(channelCode).then(ch => {
                        return ch.create("PassThrough");
                    }).then(pt => {
                        var info = {
                            model: 'seat',
                            channelCode: channelCode,
                            seatCode: seatCode,
                            createTime: (new Date()).getTime()
                        };
                        return me.registObject(pt, seatCode, '', info);
                    }).then(_seat => {
                        success(_seat);
                    });
                }

            } catch (error) {
                console.log(error);
                success(null);
            }
        });
    };
    getSingleSource(channelCode, srcUrl) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var src = Object.values(me.kmsObjs).find(obj => {
                    var flag = obj.tag && obj.tag.kmsType == "PlayerEndpoint" && obj.tag.channelCode == channelCode && obj.tag.url == srcUrl;
                    return flag;
                });
                if (src && src != null) {
                    success(src);
                } else {
                    me.getSingChannel(channelCode).then(ch => {
                        return ch.create("PlayerEndpoint", { uri: srcUrl });
                    }).then(player => {
                        var info = {
                            model: 'mediaSource',
                            channelCode: channelCode,
                            srcUrl: srcUrl,
                            createTime: (new Date()).getTime()
                        };
                        return me.registObject(player, srcUrl, '', info);
                    }).then(_seat => {
                        player.on('EndOfStream', function(eer) {
                            player.play();
                        });
                        return player.play();
                    }).then(_player => {
                        success(_player);
                    });
                }

            } catch (error) {
                console.log(error);
                success(null);
            }
        });
    };
    connectEndSeat(channelCode, seatCode, connectType, clientId, peerCode) {
        var me = this;
        var res = {};
        return new Promise((success, faild) => {
            try {
                var channel = null;
                var seat = null;
                var end = null;
                me.getSingChannel(channelCode).then(_ch => {
                    channel = _ch;
                    return me.getSingleSeat(channelCode, seatCode);
                }).then(_seat => {
                    seat = _seat;
                    return channel.create("WebRtcPoint");
                }).then(_end => {
                    end = _end;
                    var info = {
                        model: 'end',
                        channelCode: channelCode,
                        seatCode: seatCode,
                        seatKmsId: seat.id,
                        clientId: clientId,
                        peerCode: peerCode,
                        createTime: (new Date()).getTime()
                    };
                    return me.registObject(end, peerCode, clientId, info);
                }).then(_ => {
                    var src = connectType == 's' ? _end : seat;
                    var target = connectType == 'r' ? _end : seat;
                    return src.connect(target);
                }).then(_ => {
                    res = {
                        channel: channel,
                        seat: seat,
                        end: end
                    };
                    success(res);
                })
            } catch (error) {
                console.log(error);
                success(res);
            }
        });
    };
    connectSourceSeat(channelCode, seatCode, srcUrl, clientId, peerCode) {
        var me = this;
        var res = {};
        return new Promise((success, faild) => {
            try {
                var channel = null;
                var seat = null;
                var src = null;
                var end = null;
                me.getSingChannel(channelCode).then(_ch => {
                    channel = _ch;
                    return me.getSingleSeat(channelCode, seatCode);
                }).then(_seat => {
                    seat = _seat;
                    return me.getSingleSource(channelCode, srcUrl);
                }).then(_src => {
                    src = _src;
                    return player.connect(srcUrl);
                }).then(_ => {
                    return me.connectEndSeat(channelCode, seatCode, 'r', clientId, peerCode);
                }).then(_end => {
                    end = _end;
                    res = {
                        channel: channel,
                        seat: seat,
                        src: src,
                        end: end
                    };
                    success(res);
                })
            } catch (error) {
                console.log(error);
                success(res);
            }
        });
    };
    getChannelTags(channelCode) {
        var res = {
            channel: null,
            seats: [],
            sources: []
        };
        try {
            var ch = Object.values(me.kmsObjs).find(obj => {
                var flag = obj.tag && obj.tag.kmsType == "MediaPipeline" && obj.tag.code == channelCode;
                return flag;
            });
            if (ch && ch != null) {
                res.channel = ch.tag;

                var seats = Object.values(me.kmsObjs).filter(s => {
                    var flag = s.tag && s.tag.kmsType == "PassThrough" && s.id.indexOf(ch.id) == 0;
                    return flag;
                });
                res.seats = seats.map(s => {
                    return s.tag;
                });

                var sources = Object.values(me.kmsObjs).filter(s => {
                    var flag = s.tag && s.tag.kmsType == "PlayerEndpoint" && s.id.indexOf(ch.id) == 0;
                    return flag;
                });
                res.sources = sources.map(s => {
                    return s.tag;
                });
            }
        } catch (eer) {
            console.log(eer);
        }
        return res;
    };

    //----------  regist --------------------------------

    regist2WebSvr(webSvr) {
        var me = this;
        if (webSvr && webSvr != null && webSvr.registRoute) {
            var respJson = function(result, response) {
                try {
                    response.send(JSON.stringify(result));
                } catch (eer) {
                    console.log(eer);
                }
            };
            webSvr.registRoute("kms", "model", "getSvrTime", function(result, request, response) {
                try {
                    result.data = (new Date()).getTime();
                    respJson(result, response);
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "getConfig", function(result, request, response) {
                try {
                    result.data = {
                        kmsUrl: me.config.clientKmsUrl,
                        version: me.config.version,
                        peerOptions: me.config.peerOptions,
                        svrTime: (new Date()).getTime()
                    };
                    respJson(result, response);
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "clearObjs", function(result, request, response) {
                try {
                    me.clearObjs().then(_ => {
                        result.data = 1;
                        respJson(result, response);
                    });
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "closeClientObjects", function(result, request, response) {
                try {
                    var clientId = result.ps.clientId || "";
                    me.closeClientObjects(clientId).then(_ => {
                        result.data = 1;
                        respJson(result, response);
                    });
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "getChannel", function(result, request, response) {
                try {
                    var channelCode = result.ps.channelCode || "";
                    if (channelCode == "") {
                        result.data = null;
                        result.error = "channelCode is null.";
                        respJson(result, response);
                    } else {
                        result.data = me.getChannelTags(channelCode);
                        respJson(result, response);
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "connectEndSeat", function(result, request, response) {
                try {
                    //channelCode, seatCode, connectType, clientId, peerCode
                    var channelCode = result.ps.channelCode || "";
                    var seatCode = result.ps.seatCode || "";
                    var connectType = result.ps.connectType || "s";
                    var clientId = result.ps.clientId || "";
                    var peerCode = result.ps.peerCode || "";
                    me.connectEndSeat(channelCode, seatCode, connectType, clientId, peerCode).then(_res => {
                        me.result.data = me.getChannelTags();
                        me.result.data.end = _res.end ? _res.end.tag : {};
                        respJson(result, response);
                    })
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "connectSourceSeat", function(result, request, response) {
                try {
                    //channelCode, seatCode, srcUrl, clientId, peerCode
                    var channelCode = result.ps.channelCode || "";
                    var seatCode = result.ps.seatCode || "";
                    var srcUrl = result.ps.srcUrl || "";
                    var clientId = result.ps.clientId || "";
                    var peerCode = result.ps.peerCode || "";
                    me.connectSourceSeat(channelCode, seatCode, srcUrl, clientId, peerCode).then(_res => {
                        me.result.data = me.getChannelTags();
                        me.result.data.end = _res.end ? _res.end.tag : {};
                        respJson(result, response);
                    })
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "startRecord", function(result, request, response) {
                try {
                    var srcId = result.ps.srcId || "";
                    var recFn = result.ps.recFn || "";
                    if (recFn == "") {
                        result.data = null;
                        result.error = "recFn is null.";
                        respJson(result, response);
                    } else {
                        me.startRecord(srcId, recFn).then(_res => {
                            result.data = _res;
                            respJson(result, response);
                        })
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "model", "stopRecord", function(result, request, response) {
                try {
                    var recorderId = result.ps.recorderId || "";
                    if (recorderId == "") {
                        result.data = null;
                        result.error = "recorderId is null.";
                        respJson(result, response);
                    } else {
                        me.stopRecord(recorderId).then(_res => {
                            result.data = _res;
                            respJson(result, response);
                        })
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
        }
    }
};


module.exports = {
    TKmsModelSvr: TKmsModelSvr
}